package com.tender.utils.cryptography;

import com.sun.org.slf4j.internal.Logger;
import com.sun.org.slf4j.internal.LoggerFactory;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.Cipher;
import javax.crypto.spec.OAEPParameterSpec;
import javax.crypto.spec.PSource;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * 采用 RSA（非对称） + AES（对称） 混合加密、解密方式，来保证前后端的数据安全
 */
@SuppressWarnings("all")
public class RSAUtil {

    private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);

    // MAX_DECRYPT_BLOCK 应等于密钥长度/8（1 byte = 8 bit），所以当密钥位数为2048时，最大解密长度应为256.
    // 128 对应 1024，256 对应 2048
    private static final int KEY_SIZE = 2048; // 1024 已经不安全了

    // RSA最大加密明文大小
    private static final int MAX_ENCRYPT_BLOCK = 117;

    // RSA最大解密密文大小
    private static final int MAX_DECRYPT_BLOCK = 128;

    // 不仅可以使用DSA算法，同样也可以使用RSA算法做数字签名
    private static final String KEY_ALGORITHM = "RSA";
    private static final String KEY_ALGORITHM_SAFE_PADDING = "RSA/ECB/OAEPWithSHA-256AndMGF1Padding";

    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";

    // 默认种子
    public static final String DEFAULT_SEED = "$%^*%^()(!@#$CSGO!189";
    // 编码格式
    private static final String UTF_8 = "UTF-8";

    private static final Base64.Encoder base64Encoder = Base64.getEncoder();
    private static final Base64.Decoder base64Decoder = Base64.getDecoder();

    private static final String PUBLIC_KEY = "PublicKey";
    private static final String PRIVATE_KEY = "PrivateKey";

    private static final OAEPParameterSpec SPEC = new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);

    static {
        // 添加 BouncyCastleProvider 支持
        // Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 生成密钥对，密钥被 Base64 明文处理
     *
     * @param differentKeyPair ture 每次生成不一样的密钥对， false 则每次都生成一样的密钥对
     * @return
     * @throws Exception
     */
    public static Map<String, Object> initKeyBase64Str(boolean differentKeyPair) throws Exception {
        Map<String, Object> keyMap = differentKeyPair ? initKey(false) : initKey(true);
        PublicKey publicKey = (PublicKey) keyMap.get(PUBLIC_KEY);
        PrivateKey privateKey = (PrivateKey) keyMap.get(PRIVATE_KEY);
        keyMap.put(PUBLIC_KEY, new String(base64Encoder.encode(publicKey.getEncoded())));
        keyMap.put(PRIVATE_KEY, new String(base64Encoder.encode(privateKey.getEncoded())));

        return keyMap;
    }

    /**
     * 生成一对 公钥、私钥
     *
     * @param useSeed 如果使用种子，则每次返回的公钥、私钥都是不一样的。
     * @return
     * @throws Exception
     */
    public static Map<String, Object> initKey(boolean useSeed) throws Exception {
        return useSeed ? initKey(DEFAULT_SEED) : initKey(null);
    }

    /**
     * 生成密钥对：若seed为null，那么结果是随机的；若seed不为null且固定，那么结果也是固定的；
     *
     * @param seed 种子
     * @return 密钥对象
     */
    public static Map<String, Object> initKey(String seed) throws Exception {
        KeyPairGenerator keygen = KeyPairGenerator.getInstance(KEY_ALGORITHM);

        // 如果指定seed，那么secureRandom结果是一样的，所以生成的公私钥也永远不会变
        SecureRandom secureRandom = new SecureRandom();
        if (StringUtils.isNotBlank(seed)) {
            secureRandom.setSeed(seed.getBytes());
        }

        // Modulus size must range from 512 to 1024 and be a multiple of 64
        keygen.initialize(KEY_SIZE, secureRandom);

        // 生成一个密钥对，保存在keyPair中
        KeyPair keys = keygen.genKeyPair();
        PublicKey publicKey = keys.getPublic();
        PrivateKey privateKey = keys.getPrivate();

        // 将公钥和私钥保存到Map
        Map<String, Object> map = new HashMap<>(2);
        map.put(PUBLIC_KEY, publicKey);
        map.put(PRIVATE_KEY, privateKey);
        // log.info("生成密钥 = " + JSON.toJSONString(map));
        return map;
    }

    /**
     * 获取公钥 PublicKey 信息
     *
     * @param 公钥字符串
     * @return
     */
    public static PublicKey getPublicKey(String pubKeyStr) throws Exception {
        byte[] publicKeys = base64Decoder.decode(pubKeyStr);
        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeys);
        KeyFactory mykeyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = mykeyFactory.generatePublic(publicKeySpec);
        // log.info("转义后的公钥为：【" + publicKey + "】");

        return publicKey;
    }

    /**
     * 获取公钥 PrivateKey 信息
     *
     * @param 私钥字符串
     * @return
     */
    public static PrivateKey getPrivateKey(String privateKeyStr) throws Exception {
        byte[] privateKeys = base64Decoder.decode(privateKeyStr);
        PKCS8EncodedKeySpec publicKeySpec = new PKCS8EncodedKeySpec(privateKeys);
        KeyFactory mykeyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = mykeyFactory.generatePrivate(publicKeySpec);
        // log.info("转义后的私钥为：【" + privateKey + "】");

        return privateKey;
    }


    /**
     * 公钥加密，指定 RSA 方式的 PublicKey 对象
     *
     * @param content      待加密的字符串
     * @param publicKeyStr 公钥字符串
     * @param encode       编码字符
     * @return
     */
    public static String encrypt(String content, PublicKey publicKey, String encode) throws Exception {
        // RSA加密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        // cipher.init(Cipher.ENCRYPT_MODE, publicKey, SPEC);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        String encrypt = base64Encoder.encodeToString(cipher.doFinal(content.getBytes(encode)));

        return encrypt;
    }

    public static String encrypt(String content, String publicKeyStr, String encode) throws Exception {
        // base64编码的公钥
        PublicKey publicKey = getPublicKey(publicKeyStr);
        return encrypt(content, publicKey, encode);
    }

    public static String encrypt(String content, String publicKeyStr) throws Exception {
        // base64编码的公钥
        PublicKey publicKey = getPublicKey(publicKeyStr);
        return encrypt(content, publicKey, UTF_8);
    }

    public static String encrypt(String content, PublicKey publicKey) throws Exception {
        // base64编码的公钥
        return encrypt(content, publicKey, UTF_8);
    }

    public static String encryptWithPrivateKey(String content, PrivateKey privateKey) throws Exception {
        // RSA加密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        // cipher.init(Cipher.ENCRYPT_MODE, publicKey, SPEC);
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        String encrypt = base64Encoder.encodeToString(cipher.doFinal(content.getBytes(UTF_8)));
        return encrypt;
    }

    public static String encryptWithPrivateKey(String content, String privateKeyStr) throws Exception {
        PrivateKey privateKey = getPrivateKey(privateKeyStr);
        return encryptWithPrivateKey(content, privateKey);
    }


    /**
     * 私钥解密，指定 RSA 方式的 PrivateKey 对象
     *
     * @param encryptStr 加密后的字符串
     * @param privateKey 私钥
     * @return
     */
    public static String decrypt(String encryptStr, PrivateKey privateKey, String encode) throws Exception {
        // 64位解码加密后的字符串
        byte[] inputByte = base64Decoder.decode(encryptStr.getBytes(encode));
        // RSA解密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        // cipher.init(Cipher.DECRYPT_MODE, privateKey, SPEC);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        String decrypt = new String(cipher.doFinal(inputByte));

        return decrypt;
    }

    public static String decrypt(String encryptStr, String privateKeyStr, String encode) throws Exception {
        PrivateKey privateKey = getPrivateKey(privateKeyStr);
        return decrypt(encryptStr, privateKey, encode);
    }

    public static String decrypt(String encryptStr, String privateKeyStr) throws Exception {
        return decrypt(encryptStr, privateKeyStr, UTF_8);
    }

    public static String decrypt(String encryptStr, PrivateKey privateKey) throws Exception {
        return decrypt(encryptStr, privateKey, UTF_8);
    }

    public static String decryptWithPublicKey(String encryptStr, PublicKey publicKey) throws Exception {
        // 64位解码加密后的字符串
        byte[] inputByte = base64Decoder.decode(encryptStr.getBytes(UTF_8));
        // RSA解密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        // cipher.init(Cipher.DECRYPT_MODE, privateKey, SPEC);
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        String decrypt = new String(cipher.doFinal(inputByte));
        return decrypt;
    }

    public static String decryptWithPublicKey(String encryptStr, String publicKeyStr) throws Exception {
        PublicKey publicKey = getPublicKey(publicKeyStr);
        return decryptWithPublicKey(encryptStr, publicKey);
    }

    /**
     * RSA 公钥加密，【限制长度】
     *
     * @param content   待加密的字符串
     * @param publicKey 公钥字符串
     * @return 密文
     */
    public static String encryptByPublicKey(String content, PublicKey publicKey) throws Exception {
        // RSA加密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_SAFE_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey, SPEC);

        byte[] data = content.getBytes(UTF_8);
        // 加密时超过 117 字节就报错。为此采用分段加密的办法来加密
//        byte[] enBytes = null;
//        for (int i = 0; i < data.length; i += MAX_ENCRYPT_BLOCK) {
//            // 注意要使用2的倍数，否则会出现加密后的内容再解密时为乱码
//            byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
//            enBytes = ArrayUtils.addAll(enBytes, doFinal);
//        }
//        String encrypt = base64Encoder.encodeToString(enBytes);

        int inputLen = data.length;
        int offLen = 0;//偏移量
        int i = 0;
        ByteArrayOutputStream bops = new ByteArrayOutputStream();
        while (inputLen - offLen > 0) {
            byte[] cache;
            if (inputLen - offLen > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(data, offLen, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offLen, inputLen - offLen);
            }
            bops.write(cache);
            i++;
            offLen = MAX_ENCRYPT_BLOCK * i;
        }
        bops.close();
        byte[] encryptedData = bops.toByteArray();
        String encodeToString = base64Encoder.encodeToString(encryptedData);
        return encodeToString;

//        return encrypt;
    }

    /**
     * RSA 私钥解密，【限制长度】
     *
     * @param encryStr      加密字符串
     * @param privateKeyStr 私钥字符串
     * @return 明文
     */
    public static String decryptByPrivateKey(String encryStr, PrivateKey privateKey) throws Exception {
        // base64编码的私钥
        // RSA解密
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM_SAFE_PADDING);
        cipher.init(Cipher.DECRYPT_MODE, privateKey, SPEC);

        // 64位解码加密后的字符串
        byte[] data = base64Decoder.decode(encryStr);
        // 解密时超过128字节报错。为此采用分段解密的办法来解密
//        StringBuilder sb = new StringBuilder();
//        for (int i = 0; i < data.length; i += MAX_DECRYPT_BLOCK) {
//            byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_DECRYPT_BLOCK));
//            sb.append(new String(doFinal));
//        }

        int inputLen = data.length;
        int offLen = 0;
        int i = 0;
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        while (inputLen - offLen > 0) {
            byte[] cache;
            if (inputLen - offLen > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(data, offLen, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(data, offLen, inputLen - offLen);
            }
            byteArrayOutputStream.write(cache);
            i++;
            offLen = MAX_DECRYPT_BLOCK * i;
        }
        byteArrayOutputStream.close();
        byte[] byteArray = byteArrayOutputStream.toByteArray();
        return new String(byteArray);

//        return sb.toString();
    }

    public static void main(String[] args) throws Exception {
        Map<String, Object> map = initKeyBase64Str(true);
        String publicKeyStr = map.get(PUBLIC_KEY).toString();
        String privateKeyStr = map.get(PRIVATE_KEY).toString();
        System.out.println(" ***************************************** ");
        System.out.println(" ************** 测试前后端全流程 ************** ");
        System.out.println(" ***************************************** ");
        String data = "this is my handsome friend";
        System.out.println();
        System.out.println("=========== 模仿前端请求内容 ============ " + data);
        System.out.println();
        System.out.println("=========== RSA -> 公钥（publicKey）为：" + publicKeyStr);
        System.out.println();
        System.out.println("=========== RSA -> 私钥（privateKey）为：" + privateKeyStr);
        System.out.println();
        byte[] iv = AESUtil.generateIV();
        System.out.println("=========== AES -> iv 字节数组 ============ " + iv);
        System.out.println();
        String ivStr = base64Encoder.encodeToString(iv);
        System.out.println("=========== AES -> iv base64 '编码'后 ivAES ============ " + ivStr);
        System.out.println();
        byte[] ivDecode = base64Decoder.decode(ivStr);
        System.out.println("=========== AES -> iv base64 '解码'后 ============ " + ivDecode);
        System.out.println();
        String keyAES128 = AESUtil.getKeyAES_128();
        System.out.println("=========== AES -> keyAES128 加密秘钥明文 ========== " + keyAES128);
        System.out.println();
        // 给 AES 密钥 加密
        String secretAES128 = encrypt(keyAES128, publicKeyStr);
        System.out.println("=========== AES -> RSA 公钥加密后的 secretAES128 密文 ========== " + secretAES128);
        System.out.println();
        // AES 加密 请求 参数
        String contentByAES = AESUtil.encrypt(keyAES128, data, ivDecode);
        System.out.println("=========== data -> 请求内容被 AES 加密后  ========== " + contentByAES);
        System.out.println();
        // 后端 利用 RSA 密钥解密 AES 的加密/解密 密钥
        String keyAES128Decrypt = decrypt(secretAES128, privateKeyStr);
        System.out.println("============ 后端解密后的 AES 密钥 ========== " + keyAES128Decrypt);
        System.out.println();
        String decryptContent = AESUtil.decrypt(keyAES128Decrypt, contentByAES, ivDecode);
        System.out.println("============ 后端解密后的 请求内容 ========== " + decryptContent);
        System.out.println();

        System.out.println(" ***************************************** ");
        System.out.println(" ************** 测试 私钥加密 公钥解密 ************** ");
        System.out.println(" ***************************************** ");
        System.out.println();
        String str = "mon";
        String encryptWithPrivateKey = encryptWithPrivateKey(str, getPrivateKey(privateKeyStr));
        System.out.println("============ 私钥加密后 ========== " + encryptWithPrivateKey);
        System.out.println();
        String decryptWithPublicKey = decryptWithPublicKey(encryptWithPrivateKey, getPublicKey(publicKeyStr));
        System.out.println("============ 公钥加密后 ========== " + decryptWithPublicKey);
    }


}
